#pragma once

#include <pthread.h>
#include <semaphore.h>
#include <vector>

template <class T>
class circul_queue
{
public:
    // 构造函数
    circul_queue(int cap = 5)
        : _que(cap), _cap(cap)
    {
        sem_init(&_spaceSem, 0, _cap);           // 初始化生产者使用的空间信号量，初始空间资源个数为_cap
        sem_init(&_dataSem, 0, 0);               // 初始化消费者使用的数据信号量，初始数据资源个数为0
        pthread_mutex_init(&_proMutex, nullptr); // 初始化上产者使用的线程锁
        pthread_mutex_init(&_cusMutex, nullptr); // 初始化消费者使用的线程锁
        _proStep = _cusStep = 0;                 // 生产者和消费者的起始位置位于0下标处
    }

    // 析构函数
    ~circul_queue()
    {
        sem_destroy(&_spaceSem);           // 销毁生产者使用的空间信号量
        sem_destroy(&_dataSem);            // 销毁消费者使用的数据信号量
        pthread_mutex_destroy(&_proMutex); // 销毁生产者使用的线程锁
        pthread_mutex_destroy(&_cusMutex); // 销毁消费者使用的线程锁
    }

    // 生产者插入数据
    void push(const T &val)
    {
        sem_wait(&_spaceSem);             // 先申请空间信号量
                                          // 成功申请到空间信号量，则队列中至少有一个空位，可以进行生产
        pthread_mutex_lock(&_proMutex);   // 上锁，只允许一个生产者访问循环队列
        _que[_proStep++] = val;           // 往生产者当前所在的空位插入数据，然后生产者移动至下一空位
        _proStep %= _cap;                 // 生产者越界时，重新回到队头位置
        pthread_mutex_unlock(&_proMutex); // 数据插入完成，解锁
                                          // 生产了一个数据，则数据资源一定增加了1,
        sem_post(&_dataSem);              // 归还一个数据信号量
    }

    // 消费者读取数据
    T pop()
    {
        sem_wait(&_dataSem);              // 先申请数据信号量
                                          // 成功申请到数据信号量，则队列中至少有一个数据，可以进行消费
        pthread_mutex_lock(&_cusMutex);   // 上锁，只允许一个消费者访问循环队列
        T val = _que[_cusStep++];         // 取出消费者当前位置上的数据，然后消费者移动到下一个数据处
        _cusStep %= _cap;                 // 消费者越界时，重新回到队头位置
        pthread_mutex_unlock(&_cusMutex); // 消费数据完成，解锁
                                          // 消费了一个数据，则空间资源一定增加了1
        sem_post(&_spaceSem);             // 归还一个空间信号量
        return val;                       // 返回消费掉的数据
    }

private:
    vector<T> _que;            // 循环队列
    int _cap;                  // 队列容量
    int _proStep;              // 生产者停留的位置
    int _cusStep;              // 消费者停留的位置
    sem_t _spaceSem;           // 生产者使用的空间信号量
    sem_t _dataSem;            // 消费者使用的数据信号量
    pthread_mutex_t _proMutex; // 生产者使用的线程锁
    pthread_mutex_t _cusMutex; // 消费者使用的线程锁
};